﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace KBEngine.Setting
{
    [Serializable]
    [XmlRoot("root")]
    public class Entity : IXmlSerializable
    {
        public string parent = string.Empty;
        public Volatile volatilee = new Volatile();
        public List<string> interfaces = new List<string>();
        public List<Component> components = new List<Component>();
        public List<Property> properties = new List<Property>();
        public List<Method> clientMethods = new List<Method>();
        public List<Method> baseMethods = new List<Method>();
        public List<Method> cellMethods = new List<Method>();

        #region XmlSerializable
        public XmlSchema GetSchema() => null;
        public void ReadXml(XmlReader reader)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(reader);
            var root = xmlDoc.SelectSingleNode("root");
            foreach (XmlNode xmlNode in root.ChildNodes)
            {
                if (xmlNode.Name == "Parent")
                    parent = xmlNode.FirstChild.Value.Trim();
                else if (xmlNode.Name == "Volatile")
                    volatilee.ReadXmlNode(xmlNode);
                else if (xmlNode.Name == "Interfaces")
                {
                    foreach (XmlNode subNode in xmlNode.ChildNodes)
                    {
                        interfaces.Add(subNode.FirstChild.Value.Trim());
                    }
                }
                else if (xmlNode.Name == "Components")
                {
                    foreach (XmlNode subNode in xmlNode.ChildNodes)
                    {
                        components.Add(new Component().ReadXmlNode(subNode));
                    }
                }
                else if (xmlNode.Name == "Properties")
                {
                    foreach (XmlNode subNode in xmlNode.ChildNodes)
                    {
                        properties.Add(new Property().ReadXmlNode(subNode));
                    }
                }
                else if (xmlNode.Name == "ClientMethods")
                {
                    foreach (XmlNode subNode in xmlNode.ChildNodes)
                    {
                        clientMethods.Add(new Method().ReadXmlNode(subNode));
                    }
                }
                else if (xmlNode.Name == "BaseMethods")
                {
                    foreach (XmlNode subNode in xmlNode.ChildNodes)
                    {
                        baseMethods.Add(new Method().ReadXmlNode(subNode));
                    }
                }
                else if (xmlNode.Name == "CellMethods")
                {
                    foreach (XmlNode subNode in xmlNode.ChildNodes)
                    {
                        cellMethods.Add(new Method().ReadXmlNode(subNode));
                    }
                }
            }
        }
        public void WriteXml(XmlWriter writer)
        {
            XmlDocument xmlDoc = new XmlDocument();
            XmlElement root = xmlDoc.CreateElement("root");
            xmlDoc.AppendChild(root);
            XmlElement tempNode;
            if (!string.IsNullOrEmpty(parent))
            {
                tempNode = xmlDoc.CreateElement("Parent");
                tempNode.InnerText = parent;
                root.AppendChild(tempNode);
            }
            volatilee.WriteXmlNode(root);
            if (interfaces.Count > 0)
            {
                tempNode = xmlDoc.CreateElement("Interfaces");
                root.AppendChild(tempNode);
                foreach (var item in interfaces)
                {
                    var tempSubNode = xmlDoc.CreateElement("Interface");
                    tempSubNode.InnerText = item;
                    tempNode.AppendChild(tempSubNode);
                }
            }
            if (components.Count > 0)
            {
                tempNode = xmlDoc.CreateElement("Components");
                root.AppendChild(tempNode);
                foreach (var item in components)
                {
                    item.WriteXmlNode(tempNode);
                }
            }
            if (properties.Count > 0)
            {
                tempNode = xmlDoc.CreateElement("Properties");
                root.AppendChild(tempNode);
                foreach (var item in properties)
                {
                    item.WriteXmlNode(tempNode);
                }
            }
            if (clientMethods.Count > 0)
            {
                tempNode = xmlDoc.CreateElement("ClientMethods");
                root.AppendChild(tempNode);
                foreach (var item in clientMethods)
                {
                    item.WriteXmlNode(tempNode);
                }
            }
            if (baseMethods.Count > 0)
            {
                tempNode = xmlDoc.CreateElement("BaseMethods");
                root.AppendChild(tempNode);
                foreach (var item in baseMethods)
                {
                    item.WriteXmlNode(tempNode);
                }
            }
            if (cellMethods.Count > 0)
            {
                tempNode = xmlDoc.CreateElement("CellMethods");
                root.AppendChild(tempNode);
                foreach (var item in cellMethods)
                {
                    item.WriteXmlNode(tempNode);
                }
            }
            foreach (XmlNode item in root.ChildNodes)
            {
                item.WriteTo(writer);
            }
        }
        #endregion


        [Serializable]
        public class Component
        {
            public string componentName = string.Empty;
            public string componentType = string.Empty;
            public bool persistent = false;

            #region XmlSerializable
            public Component ReadXmlNode(XmlNode xmlNode)
            {
                componentName = xmlNode.Name;
                if (xmlNode.SelectSingleNode("Type").TryGetValue(out var txt))
                    componentType = txt;

                if (xmlNode.SelectSingleNode("Persistent").TryGetValue(out txt))
                    persistent = bool.Parse(txt);

                return this;
            }
            public void WriteXmlNode(XmlNode xmlNode)
            {
                var propertyNode = xmlNode.OwnerDocument.CreateElement(componentName);
                xmlNode.AppendChild(propertyNode);

                if (!string.IsNullOrEmpty(componentType))
                {
                    var tempNode = propertyNode.OwnerDocument.CreateElement("Type");
                    tempNode.InnerText = componentType;
                    propertyNode.AppendChild(tempNode);
                }
                if (persistent)
                {
                    var tempNode = propertyNode.OwnerDocument.CreateElement("Persistent");
                    tempNode.InnerText = "true";
                    propertyNode.AppendChild(tempNode);
                }
            }
            #endregion
        }
        [Serializable]
        public class Volatile
        {
            public float position = 0;
            public float yaw = 0;
            public float pitch = 0;
            public float roll = 0;
            public bool optimized = false;

            #region XmlSerializable
            public Volatile ReadXmlNode(XmlNode xmlNode)
            {
                if (xmlNode.SelectSingleNode("position").TryGetValue(out var txt))
                    position = float.Parse(txt);

                if (xmlNode.SelectSingleNode("yaw").TryGetValue(out txt))
                    yaw = float.Parse(txt);

                if (xmlNode.SelectSingleNode("pitch").TryGetValue(out txt))
                    pitch = float.Parse(txt);

                if (xmlNode.SelectSingleNode("roll").TryGetValue(out txt))
                    roll = float.Parse(txt);

                if (xmlNode.SelectSingleNode("optimized").TryGetValue(out txt))
                    optimized = bool.Parse(txt);
                return this;
            }
            public void WriteXmlNode(XmlNode xmlNode)
            {
                XmlNode xmlNodeRoot = null;
                if (position != 0)
                {
                    xmlNodeRoot = WriteNodeRoot(xmlNodeRoot, xmlNode);
                    var tempNode = xmlNodeRoot.OwnerDocument.CreateElement("position");
                    tempNode.InnerText = position.ToString();
                    xmlNodeRoot.AppendChild(tempNode);
                }
                if (yaw != 0)
                {
                    xmlNodeRoot = WriteNodeRoot(xmlNodeRoot, xmlNode);
                    var tempNode = xmlNodeRoot.OwnerDocument.CreateElement("yaw");
                    tempNode.InnerText = yaw.ToString();
                    xmlNodeRoot.AppendChild(tempNode);
                }
                if (pitch != 0)
                {
                    xmlNodeRoot = WriteNodeRoot(xmlNodeRoot, xmlNode);
                    var tempNode = xmlNodeRoot.OwnerDocument.CreateElement("pitch");
                    tempNode.InnerText = pitch.ToString();
                    xmlNodeRoot.AppendChild(tempNode);
                }
                if (roll != 0)
                {
                    xmlNodeRoot = WriteNodeRoot(xmlNodeRoot, xmlNode);
                    var tempNode = xmlNodeRoot.OwnerDocument.CreateElement("roll");
                    tempNode.InnerText = roll.ToString();
                    xmlNodeRoot.AppendChild(tempNode);
                }
                if (optimized)
                {
                    xmlNodeRoot = WriteNodeRoot(xmlNodeRoot, xmlNode);
                    var tempNode = xmlNodeRoot.OwnerDocument.CreateElement("optimized");
                    tempNode.InnerText = optimized.ToString();
                    xmlNodeRoot.AppendChild(tempNode);
                }
            }
            private XmlNode WriteNodeRoot(XmlNode xmlNodeRoot, XmlNode parentNode)
            {
                if (xmlNodeRoot != null)
                    return xmlNodeRoot;

                xmlNodeRoot = parentNode.OwnerDocument.CreateElement("Volatile");
                parentNode.AppendChild(xmlNodeRoot);
                return xmlNodeRoot;
            }
            #endregion
        }
        [Serializable]
        public class Property
        {
            public string propertyName = string.Empty;

            /// <summary>
            /// 需要通过 Types.GetType 与 Types.GetTypeStr 进行类型序列化
            /// </summary>
            public string type = string.Empty;
            public string defaultVal = string.Empty;
            public int uType = 0;
            public string flags = string.Empty;
            public bool persistent = false;
            public uint databaseLength = 0;
            public string index = string.Empty;

            public override string ToString()
            {
                return string.Format("{0}: {1} = {2}", propertyName, type, defaultVal);
            }

            #region XmlSerializable
            public Property ReadXmlNode(XmlNode xmlNode)
            {
                propertyName = xmlNode.Name;
                if (xmlNode.SelectSingleNode("Type").TryGetValue(out var txt))
                    type = txt;
                if (type == "ARRAY")
                {
                    xmlNode.SelectSingleNode("Type").SelectSingleNode("of").TryGetValue(out type);
                    type = Types.ARRAY_HEAD + type;
                }

                if (xmlNode.SelectSingleNode("Default").TryGetValue(out txt))
                    defaultVal = txt;

                if (xmlNode.SelectSingleNode("Utype").TryGetValue(out txt))
                    uType = int.Parse(txt);

                if (xmlNode.SelectSingleNode("Flags").TryGetValue(out txt))
                    flags = txt;

                if (xmlNode.SelectSingleNode("Persistent").TryGetValue(out txt))
                    persistent = bool.Parse(txt);

                if (xmlNode.SelectSingleNode("DatabaseLength").TryGetValue(out txt))
                    databaseLength = uint.Parse(txt);

                if (xmlNode.SelectSingleNode("Index").TryGetValue(out txt))
                    index = txt;
                return this;
            }
            public void WriteXmlNode(XmlNode xmlNode)
            {
                var propertyNode = xmlNode.OwnerDocument.CreateElement(propertyName);
                xmlNode.AppendChild(propertyNode);

                if (!string.IsNullOrEmpty(type))
                { 
                    var tempNode = propertyNode.OwnerDocument.CreateElement("Type");
                    if(type.StartsWith(Types.ARRAY_HEAD))
                        tempNode.InnerXml = string.Format(" ARRAY <of> {0} </of>", type.Remove(0, Types.ARRAY_HEAD.Length));
                    else
                        tempNode.InnerText = type;
                    propertyNode.AppendChild(tempNode);
                }
                if (!string.IsNullOrEmpty(defaultVal))
                {
                    var tempNode = propertyNode.OwnerDocument.CreateElement("Default");
                    tempNode.InnerText = defaultVal;
                    propertyNode.AppendChild(tempNode);
                }
                if (uType != 0)
                {
                    var tempNode = propertyNode.OwnerDocument.CreateElement("Utype");
                    tempNode.InnerText = uType.ToString();
                    propertyNode.AppendChild(tempNode);
                }
                if (!string.IsNullOrEmpty(flags))
                {
                    var tempNode = propertyNode.OwnerDocument.CreateElement("Flags");
                    tempNode.InnerText = flags;
                    propertyNode.AppendChild(tempNode);
                }
                if (persistent)
                {
                    var tempNode = propertyNode.OwnerDocument.CreateElement("Persistent");
                    tempNode.InnerText = "true";
                    propertyNode.AppendChild(tempNode);
                }
                if (databaseLength != 0)
                {
                    var tempNode = propertyNode.OwnerDocument.CreateElement("DatabaseLength");
                    tempNode.InnerText = databaseLength.ToString();
                    propertyNode.AppendChild(tempNode);
                }
                if (!string.IsNullOrEmpty(index))
                {
                    var tempNode = propertyNode.OwnerDocument.CreateElement("Index");
                    tempNode.InnerText = index.ToString();
                    propertyNode.AppendChild(tempNode);
                }
            }
            #endregion
        }
        [Serializable]
        public class Method 
        {
            public string methodName = string.Empty;
            public bool exposed = false;
            public ushort uType = 0;

            /// <summary>
            /// 需要通过 Types.GetType 与 Types.GetTypeStr 进行类型序列化
            /// </summary>
            public List<string> args = new List<string>();

            public override string ToString()
            {
                return string.Format("{0} {1}({2})", exposed ? 'E' : ' ', methodName, string.Join(", ", args));
            }

            #region XmlSerializable
            public Method ReadXmlNode(XmlNode xmlNode)
            {
                methodName = xmlNode.Name;
                var tempNode = xmlNode.SelectSingleNode("Exposed");
                if (tempNode != null)
                    exposed = true;

                tempNode = xmlNode.SelectSingleNode("Utype");
                if (tempNode != null)
                    uType = ushort.Parse(tempNode.FirstChild.Value.Trim());

                var tempNodeList = xmlNode.SelectNodes("Arg");
                if (tempNodeList != null)
                {
                    foreach (XmlNode subNode in tempNodeList)
                    {
                        string type;
                        subNode.TryGetValue(out type);
                        if (type == "ARRAY")
                        {
                            subNode.SelectSingleNode("of").TryGetValue(out type);
                            type = Types.ARRAY_HEAD + type;
                        }
                        args.Add(type);
                    }
                }
                return this;
            }
            public void WriteXmlNode(XmlNode xmlNode)
            {
                var propertyNode = xmlNode.OwnerDocument.CreateElement(methodName);
                xmlNode.AppendChild(propertyNode);

                if (exposed)
                {
                    var tempNode = propertyNode.OwnerDocument.CreateElement("Exposed");
                    propertyNode.AppendChild(tempNode);
                }
                if (uType != 0)
                {
                    var tempNode = propertyNode.OwnerDocument.CreateElement("Utype");
                    tempNode.InnerText = uType.ToString();
                    propertyNode.AppendChild(tempNode);
                }
                foreach (var item in args)
                {
                    var tempNode = propertyNode.OwnerDocument.CreateElement("Arg");
                    if (item.StartsWith(Types.ARRAY_HEAD))
                        tempNode.InnerXml = string.Format(" ARRAY <of> {0} </of>", item.Remove(0, Types.ARRAY_HEAD.Length));
                    else
                        tempNode.InnerText = item;
                    propertyNode.AppendChild(tempNode);
                }
            }
            #endregion
        }
    }
}